﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Text.RegularExpressions;

namespace MusicRenamer
{
    public partial class frmMain : Form
    {

        public frmMain()
        {
            InitializeComponent();
        }

        private void btnStart_Click(object sender, EventArgs e)
        {
            if (btnStart.Text == "Cancel")
            {
                bgWorker.CancelAsync();
                lblProc.Visible = false;
                pgBar.Visible = false;
                //lblFileName.Visible = false;
                btnStart.Text = "Start";
                Log("Process Cancelled");
                Log("Time: " + DateTime.Now.ToString());
            }
            else
            {
                #region Validation
                string errMsg = null;
                if (string.IsNullOrEmpty(txtMusicLoc.Text))
                {
                    errMsg = "Please specify a directory";
                }

                else if (!Directory.Exists(txtMusicLoc.Text))
                {
                    errMsg = "The specified directory does not exist";
                }

                else if (Directory.GetFiles(txtMusicLoc.Text).Count() == 0)
                {
                    errMsg = "There are no files listed within the specified directory";
                }

                if (!string.IsNullOrEmpty(errMsg))
                {
                    MessageBox.Show(errMsg, "Invalid Directory", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                    return;
                }
                #endregion

                pgBar.Visible = true;
                lblProc.Visible = true;
                //lblFileName.Visible = true;
                btnStart.Text = "Cancel";
                btnRestore.Visible = false;
                btnDelBackup.Visible = false;
                string[] files = Directory.GetFiles(txtMusicLoc.Text);
                pgBar.Minimum = 0;
                pgBar.Maximum = files.Count();
                
                Log(string.Empty);
                Log("Started processing directory: \"" + txtMusicLoc.Text + "\"");
                Log("Time: " + DateTime.Now.ToString());

                bgWorker.RunWorkerAsync();
            }
        }

        private void btnBrowse_Click(object sender, EventArgs e)
        {
            fbdMusic.ShowDialog();
            txtMusicLoc.Text = fbdMusic.SelectedPath;
            btnRestore.Visible = false;
        }

        private void CopyDirectory(DirectoryInfo source, DirectoryInfo destination)
        {
            if (!destination.Exists)
            {
                destination.Create();
            }

            // Copy all music files.
            FileInfo[] files = source.GetFiles();
            foreach (FileInfo file in files)
            {
                string filePath = Path.Combine(destination.FullName, file.Name);
                string fileExt = file.Name.Substring(file.Name.LastIndexOf(".") + 1);
                if (fileExt.Contains(Helpers.musicExt)) //only backup music files
                {
                    if (!File.Exists(filePath))
                    {
                        file.CopyTo(filePath);
                    }
                }
            }
        }

        private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            if (chkBkp.Checked)
            {
                bgWorker.ReportProgress(0, new UserState { Label = "Creating Backup" });
                DirectoryInfo source = new DirectoryInfo(txtMusicLoc.Text);
                DirectoryInfo destination = new DirectoryInfo(txtMusicLoc.Text + "\\Backup");
                CopyDirectory(source, destination);
            }

            
            string[] files = Directory.GetFiles(txtMusicLoc.Text);

            for (int i = 0; i<files.Count(); i++)
            {
                BackgroundWorker worker = sender as BackgroundWorker;
                if (worker.CancellationPending)
                {
                    e.Cancel = true;
                    return;
                }

                string ext = files[i].Substring(files[i].LastIndexOf(".") + 1);
                string dir = files[i].Substring(0, files[i].LastIndexOf("\\"));
                string fileName = files[i].Substring(files[i].LastIndexOf("\\") + 1);

                MusicInfo music = new MusicInfo
                {
                    Directory = dir,
                    FileExtension = ext,
                    FileIndex = i,
                    FileList = files,
                    FileName = fileName,
                    Worker = bgWorker
                };

                if (ext.ToLower() == "mp3")
                {
                    //try to get the IDv3 tags
                    TagLib.File mp3;
                    try 
                    {
                        mp3 = TagLib.File.Create(files[i]);
                        music.mp3 = mp3;
                    }
                    catch 
                    {
                        mp3 = null;
                    }



                    if (mp3 != null)
                    {
                        uint id3TrackNo = mp3.Tag.Track;
                        string id3Title = mp3.Tag.Title;
                        string id3TrackNum;

                        if (!string.IsNullOrEmpty(id3Title) && chkUseID3.Checked)
                        {
                            //if the idv3 tag track number is 0 (null)
                            //then attempt to get the track number from the file name
                            if (id3TrackNo == 0)
                            {
                                Mp3 fileMp3 = fileName.FormatMp3(music.Directory, music.FileExtension, chkUseDiscFolders.Checked);
                                if (fileMp3.TrackNumber != "0")
                                {
                                    id3TrackNo = uint.Parse(fileMp3.TrackNumber);

                                    //save the new track number
                                    //back to the idv3 tag
                                    mp3.Tag.Track = id3TrackNo;
                                    mp3.Save();
                                }
                            }

                            //if the track number is less than ten, then insert a zero (0)
                            id3TrackNum = id3TrackNo >= 10 ? id3TrackNo.ToString() : "0" + id3TrackNo.ToString();

                            //remove illegal characters and extrraneous spaces/underscores
                            id3Title = id3Title.FormatTitle(chkRemoveText.Checked, txtRemoveText.Text);

                            //create the new file name including folder path

                            
                            string newMp3FileName = id3TrackNum + " " + id3Title + "." + music.FileExtension;
                            string fullMp3FileName = null;// = music.Directory + "\\" + newMp3FileName;
                            string discDir = null;

                            if (chkUseDiscFolders.Checked)
                            {
                                Mp3 discMp3 = fileName.FormatMp3(dir, ext, true);
                                if (discMp3.DiscNumber != 0)
                                {
                                    discDir = music.Directory + "\\Disc " + discMp3.DiscNumber.ToString();
                                    //fullMp3FileName = music.Directory + "\\Disc " + discMp3.DiscNumber.ToString() + "\\" + newMp3FileName;
                                }
                            }

                            if (discDir != null)
                            {
                                if (!Directory.Exists(discDir))
                                {
                                    Directory.CreateDirectory(discDir);
                                }
                                fullMp3FileName = discDir + "\\" + newMp3FileName;
                            }
                            else
                            {
                                fullMp3FileName = music.Directory + "\\" + newMp3FileName;
                            }
                            
                            

                            //"rename" the file
                            File.Move(music.FileList[music.FileIndex], fullMp3FileName);

                            //report progress
                            bgWorker.ReportProgress(music.FileIndex, new UserState { FileName = music.FileName, Label = "Processing Files", NewFileName = newMp3FileName });

                            //use for testing progress bar
                            //System.Threading.Thread.Sleep(1000);
                        }
                        else //the title is null, run regex using fileName
                        {
                            UseRegex(music);
                        }
                    }
                    else
                    { 
                        //the mp3 id3 tags were null
                        //still try to rename the file using regex
                        UseRegex(music);
                    }
                }
                else if(ext.Contains(Helpers.musicExt))
                {
                    //not an mp3, but still music,
                    //use regex to name file
                    UseRegex(music);
                }
            }
        }


        private void UseRegex(MusicInfo music)
        {
            string fullNewMp3FileName = null;
            string newMp3FileName = null;
            //remove illegal characters and extrraneous spaces/underscores
            newMp3FileName = music.FileName.FormatTitle(chkRemoveText.Checked, txtRemoveText.Text);

            Mp3 newMp3 = newMp3FileName.FormatMp3(music.Directory, music.FileExtension, chkUseDiscFolders.Checked);

            if (newMp3 != null)
            {
                if (music.mp3 != null)
                {
                    //save title and track number
                    music.mp3.Tag.Title = newMp3.Title;
                    music.mp3.Tag.Track = uint.Parse(newMp3.TrackNumber);
                    music.mp3.Save();
                }

                string discDir = music.Directory + "\\Disc " + newMp3.DiscNumber.ToString();
                newMp3FileName = newMp3.TrackNumber + " " + newMp3.Title + "." + newMp3.Extension;


                if (newMp3.DiscNumber != 0)
                {
                    if (!Directory.Exists(discDir))
                    {
                        Directory.CreateDirectory(discDir);
                    }
                    fullNewMp3FileName = discDir + "\\" + newMp3FileName;
                }
                else
                {
                    fullNewMp3FileName = music.Directory + "\\" + newMp3FileName;

                }
            }

            if (!string.IsNullOrEmpty(newMp3FileName))
            {
                //"rename" the file
                File.Move(music.FileList[music.FileIndex], fullNewMp3FileName);
            }

            //report progress
            music.Worker.ReportProgress(music.FileIndex, new UserState { FileName = music.FileName, Label = "Processing Files", NewFileName = newMp3FileName });
        }

        private void Log(string addLog)
        {
            lbLog.Visible = true;
            lbLog.Items.Add(addLog);
            lbLog.SelectedIndex = lbLog.Items.Count - 1;

            string appPath = Application.ExecutablePath.Substring(0, Application.ExecutablePath.LastIndexOf("\\"));
            StreamWriter write = File.AppendText(appPath + "\\Log.txt");
            write.WriteLine(addLog);
            write.Flush();
            write.Close();
        }

        private void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            UserState state = (UserState)e.UserState;
            lblProc.Text = state.Label;
            pgBar.Value = e.ProgressPercentage;
            string lbAdd = "{0}   =>   {1}";

            if (!string.IsNullOrEmpty(state.FileName) && !string.IsNullOrEmpty(state.NewFileName))
            {
                if (state.FileName.ToLower() == state.NewFileName.ToLower())
                {
                    Log(string.Format(lbAdd, state.FileName, "No Change"));
                }
                else
                {
                    Log(string.Format(lbAdd, state.FileName, state.NewFileName));
                }
            }
            else
            {
                Log(state.Label);
            }

            lbLog.SelectedIndex = lbLog.Items.Count - 1;
        }

        private void bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Cancelled)
            {
                Log("Renaming process was aborted by user");
                Log("Time: " + DateTime.Now.ToString());
                MessageBox.Show("Process was cancelled", "Aborted", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
            else if (e.Error != null)
            {
                Log("An error occurred while during the renaming process");
                Log(e.Error.Message.ToString());
                Log("Time: " + DateTime.Now.ToString());
                MessageBox.Show("An error occurred while renaming files\n" + e.Error.Message.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            else
            {
                Log("Completed processing: \"" + txtMusicLoc.Text + "\"");
                Log("Time: " + DateTime.Now.ToString());

                lblProc.Visible = false;
                pgBar.Visible = false;
                btnStart.Text = "Start";
                //txtMusicLoc.Text = null;
                pgBar.Value = 0;
                txtRemoveText.Text = null;
                txtRemoveText.Visible = false;
                chkRemoveText.Checked = false;

                btnRestore.Visible = chkBkp.Checked;
                btnDelBackup.Visible = chkBkp.Checked;

                MessageBox.Show("Process completed successfully", "Finished", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
        }

        private void chkRemoveText_CheckedChanged(object sender, EventArgs e)
        {
            if (chkRemoveText.Checked)
            {
                txtRemoveText.Visible = true;
                lblRemove.Visible = true;
            }
            else
            {
                txtRemoveText.Visible = false;
                lblRemove.Visible = false;
            }
        }

        private void frmMain_Load(object sender, EventArgs e)
        {
            string appPath = Application.ExecutablePath.Substring(0, Application.ExecutablePath.LastIndexOf("\\"));

            while (!File.Exists(appPath + "\\taglib-sharp.dll"))
            {
                DialogResult result = MessageBox.Show("Missing required DLL: taglib-sharp.dll", null, MessageBoxButtons.RetryCancel, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button1);
                if (result == DialogResult.Cancel)
                {
                    Environment.Exit(0);
                }
            }

            this.Text += " v" + Application.ProductVersion;
        }

        private class UserState
        {
            public string FileName { get; set; }
            public string NewFileName { get; set; }
            public string Label { get; set; }
        }

        private void btnRestore_Click(object sender, EventArgs e)
        {
            if (btnRestore.Text == "Cancel Restore")
            {
                bgWorker.CancelAsync();
                lblProc.Visible = false;
                pgBar.Visible = false;
                //lblFileName.Visible = false;
                btnRestore.Text = "Restore Backup";
                Log("Restore Cancelled");
                Log("Time: " + DateTime.Now.ToString());
            }
            else
            {
                DirectoryInfo dir = new DirectoryInfo(txtMusicLoc.Text);
                DirectoryInfo bkpDir = new DirectoryInfo(dir.FullName + "\\Backup");

                //make sure both directories still exist
                if (Directory.Exists(bkpDir.FullName) && Directory.Exists(dir.FullName))
                {
                    //create new background worker
                    BackgroundWorker bgRestore = new BackgroundWorker
                    {
                        WorkerReportsProgress = true,
                        WorkerSupportsCancellation = true
                    };

                    bgRestore.ProgressChanged += new ProgressChangedEventHandler(bgRestore_ProgressChanged);
                    bgRestore.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgRestore_RunWorkerCompleted);
                    bgRestore.DoWork += new DoWorkEventHandler(bgRestore_DoWork);

                    //write log
                    Log(string.Empty);
                    Log("Started restoring: " + txtMusicLoc.Text + "\\Backup  =>  " + txtMusicLoc.Text);
                    Log("Time: " + DateTime.Now.ToString());

                    btnRestore.Text = "Cancel Restore";

                Retry:
                    int dirCount = 0;
                    int bkpCount = 0;
                    int fileCount = 0;

                    //get a count of each music file
                    foreach (FileInfo file in dir.GetFiles())
                    {
                        if (file.Extension.Contains(Helpers.musicExt))
                        {
                            dirCount += 1;
                        }
                    }

                    if (dirCount == 0)
                    { 
                        int discNo = 1;
                        string discDir = string.Format("{0}\\Disc {1}", dir.FullName, discNo);
                        
                        while (Directory.Exists(discDir))
                        {
                            DirectoryInfo discDirectory = new DirectoryInfo(discDir);
                            foreach (FileInfo file in discDirectory.GetFiles())
                            {
                                if (file.Extension.Contains(Helpers.musicExt))
                                {
                                    dirCount += 1;
                                }
                            }

                            discNo += 1;
                            discDir = string.Format("{0}\\Disc {1}", dir.FullName, discNo);
                        }
                    }

                    foreach (FileInfo file in bkpDir.GetFiles())
                    {
                        if (file.Extension.Contains(Helpers.musicExt))
                        {
                            bkpCount += 1;
                        }
                    }


                    if (bkpCount != dirCount)
                    {
                        DialogResult result = MessageBox.Show("The number of music files in the backup directory does not match that of the parent directory",
                            "Invalid File Count", MessageBoxButtons.AbortRetryIgnore, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button1);
                        switch (result)
                        {
                            case DialogResult.Abort:
                                btnRestore.Text = "Restore Backup";
                                return;

                            case DialogResult.Retry:
                                goto Retry;
                        }
                    }

                    fileCount = bkpCount + dirCount;
                    pgBar.Visible = true;
                    pgBar.Minimum = 0;
                    pgBar.Maximum = fileCount;
                    bgRestore.RunWorkerAsync();
                }
                else
                {
                    MessageBox.Show("Unable to locate backup directory", "No Backup Found", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                }
            }
        }

        void bgRestore_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker worker = sender as BackgroundWorker;
            DirectoryInfo dir = new DirectoryInfo(txtMusicLoc.Text);
            DirectoryInfo bkpDir = new DirectoryInfo(dir.FullName + "\\Backup");

            int progress = 0;

            //delete files
            foreach (FileInfo file in dir.GetFiles())
            {
                //check for cancel click
                if (worker.CancellationPending)
                {
                    e.Cancel = true;
                    return;
                }

                if (file.Extension.Contains(Helpers.musicExt))
                {
                    File.Delete(file.FullName);
                    progress += 1;
                    worker.ReportProgress(progress, new UserState { Label = "Deleting Files", FileName = file.FullName });
                }
            }

            int discNo = 1;
            string discDir = string.Format("{0}\\Disc {1}", dir.FullName, discNo);

            //delete disc directories if they exist
            while (Directory.Exists(discDir))
            {
                Directory.Delete(discDir, true);
                worker.ReportProgress(progress, new UserState { Label = "Deleting Folder", FileName = discDir });

                discNo += 1;
                discDir = string.Format("{0}\\Disc {1}", dir.FullName, discNo);
            }

            //copy files
            foreach (FileInfo file in bkpDir.GetFiles())
            {
                //check for cancel click
                if (worker.CancellationPending)
                {
                    e.Cancel = true;
                    return;
                }

                if (file.Extension.Contains(Helpers.musicExt))
                {
                    //copy to parent directory
                    file.CopyTo(dir.FullName + "\\" + file.Name);
                    progress += 1;
                    worker.ReportProgress(progress, new UserState { Label = "Restoring Files", FileName = file.Name, NewFileName = dir.FullName + "\\" + file.Name });
                }
            }

            //delete the backup directory
            Directory.Delete(bkpDir.FullName, true);
        }

        void bgRestore_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            UserState state = (UserState)e.UserState;
            lblProc.Text = state.Label;
            pgBar.Value = e.ProgressPercentage;
            string restore = "Restoring File: {0}   =>   {1}";
            string delete = "Deleting File: {0}";

            if (!string.IsNullOrEmpty(state.FileName) && !string.IsNullOrEmpty(state.NewFileName))
            {
                Log(string.Format(restore, state.FileName, state.NewFileName));
            }
            else if(!string.IsNullOrEmpty(state.FileName))
            {
                Log(string.Format(delete, state.FileName));
            }

            lbLog.SelectedIndex = lbLog.Items.Count - 1;
        }

        void bgRestore_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Cancelled)
            {
                Log("Aborted restore: " + txtMusicLoc.Text + "\\Backup  =>  " + txtMusicLoc.Text);
                Log("Time: " + DateTime.Now.ToString());
                MessageBox.Show("Process was cancelled", "Aborted", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
            else if (e.Error != null)
            {
                Log("An error occurred while performing restore");
                Log(e.Error.Message.ToString());
                MessageBox.Show("An error occurred while restoring files\n" + e.Error.Message.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            else
            {
                Log("Completed restore: " + txtMusicLoc.Text + "\\Backup  =>  " + txtMusicLoc.Text);
                Log("Time: " + DateTime.Now.ToString());

                lblProc.Visible = false;
                pgBar.Visible = false;
                btnStart.Text = "Start";
                btnRestore.Text = "Restore Backup";
                btnRestore.Visible = false;

                txtMusicLoc.Text = null;
                pgBar.Value = 0;
                txtRemoveText.Text = null;
                txtRemoveText.Visible = false;
                chkRemoveText.Checked = false;
                btnDelBackup.Visible = false;

                MessageBox.Show("Restore completed successfully", "Finished", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
        }

        private void btnDelBackup_Click(object sender, EventArgs e)
        {
            DialogResult result = MessageBox.Show("Are you sure you want to delete the backup folder?", "Confirm Deletion", MessageBoxButtons.OKCancel, MessageBoxIcon.Warning);
            if (result == System.Windows.Forms.DialogResult.OK)
            {
                if (Directory.Exists(txtMusicLoc.Text + "\\Backup"))
                {
                    try
                    {
                        Directory.Delete(txtMusicLoc.Text + "\\Backup", true);

                        btnDelBackup.Visible = false;
                        btnRestore.Visible = false;

                        MessageBox.Show("Backup directory deleted successfully");
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show(ex.Message.ToString(), "Unable to Delete Backup Directory", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    }
                }
                else
                {
                    MessageBox.Show("Backup directory could not be found", "Directory Not Found", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                }
            }
        }
    }
}
